#include <stdio.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <unistd.h>
#include <signal.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/time.h>
#include <string.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/time.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <linux/types.h>
#include <linux/if_ether.h>
#include <linux/if_packet.h>
#include <linux/if_arp.h>
#include <linux/if_packet.h>

#define PACKET_SEND_MAX_NUM 6400

typedef struct ping_packet_status
{
    struct timeval begin_time;
    struct timeval end_time;
    int flag;   //发送标志,1为已发送
    int seq;     //包的序列号
}ping_packet_status;

/*校验和算法*/
unsigned short cal_chksum(unsigned short *addr,int len)
{       int nleft=len;
        int sum=0;
        unsigned short *w=addr;
        unsigned short answer=0;

        /*把ICMP报头二进制数据以2字节为单位累加起来*/
        while(nleft>1)
        {
            sum+=*w++;
            nleft-=2;
        }
        /*若ICMP报头为奇数个字节，会剩下最后一字节。把最后一个字节视为一个2字节数据的高字节，这个2字节数据的低字节为0，继续累加*/
        if( nleft==1)
        {
            *(unsigned char *)(&answer)=*(unsigned char *)w;
            sum+=answer;
        }
        sum=(sum>>16)+(sum&0xffff);
        sum+=(sum>>16);
        answer=~sum;
        return answer;
}

struct timeval cal_time_offset(struct timeval begin, struct timeval end)
{
    struct timeval ans;
    ans.tv_sec = end.tv_sec - begin.tv_sec;
    ans.tv_usec = end.tv_usec - begin.tv_usec;
    if(ans.tv_usec < 0) //如果接收时间的usec小于发送时间的usec，则向sec域借位
    {
        ans.tv_sec--;
        ans.tv_usec+=1000000;
    }
    return ans;
}

void icmp_pack(struct icmp* icmphdr, int seq, int length)
{
    int i = 0;

    icmphdr->icmp_type = ICMP_ECHO;
    icmphdr->icmp_code = 0;
    icmphdr->icmp_cksum = 0;
    icmphdr->icmp_seq = seq;
    icmphdr->icmp_id = getpid() & 0xffff;
    for(i=0;i<length;i++)
    {
        icmphdr->icmp_data[i] = i;
    }

    icmphdr->icmp_cksum = cal_chksum((unsigned short*)icmphdr, length);
}

int icmp_unpack(char* buf, int len, ping_packet_status *ping_packet)
{
    int iphdr_len;
    struct timeval begin_time, recv_time, offset_time;
    int rtt;  //round trip time

    struct ip* ip_hdr = (struct ip *)buf;
    iphdr_len = ip_hdr->ip_hl*4;
    struct icmp* icmp = (struct icmp*)(buf+iphdr_len);
    len-=iphdr_len;  //icmp包长度
    if(len < 8)   //判断长度是否为ICMP包长度
    {
        printf("Invalid icmp packet.Its length is less than 8\n");
        return -1;
    }

    //判断该包是ICMP回送回答包且该包是我们发出去的
    if((icmp->icmp_type == ICMP_ECHOREPLY) && (icmp->icmp_id == (getpid() & 0xffff)))
    {
        if((icmp->icmp_seq < 0) || (icmp->icmp_seq > PACKET_SEND_MAX_NUM))
        {
            printf("icmp packet seq is out of range!\n");
            return -1;
        }

        ping_packet->flag = 0;
        begin_time = ping_packet->begin_time;
        gettimeofday(&recv_time, NULL);

        offset_time = cal_time_offset(begin_time, recv_time);
        rtt = offset_time.tv_sec*1000 + offset_time.tv_usec/1000; //毫秒为单位

        printf("%d byte from %s: icmp_seq=%u ttl=%d rtt=%d ms\n",
            len, inet_ntoa(ip_hdr->ip_src), icmp->icmp_seq, ip_hdr->ip_ttl, rtt);

    }
    else
    {
        printf("Invalid ICMP packet! Its id is not matched!\n");
        return -1;
    }
    return rtt;
}

int ping_send_(int rawsock, ping_packet_status *ping_packet, struct sockaddr_in *dest)
{
    char send_buf[128];
    memset(send_buf, 0, sizeof(send_buf));

    int size = 0;
    gettimeofday(&(ping_packet->begin_time), NULL);
    ping_packet->flag = 1; //将该标记为设置为该包已发送

    icmp_pack((struct icmp*)send_buf, 0, 64); //封装icmp包
    size = sendto(rawsock, send_buf, 64, 0, (struct sockaddr*)dest, sizeof(struct sockaddr));
    if(size < 0)
    {
        printf("send icmp packet fail!\n");
        return -1;
    }
    
    return 0;
}

int ping_recv_(int rawsock, ping_packet_status *ping_packet)
{
    struct timeval tv;
    tv.tv_usec = 1000000;  //设置select函数的超时时间为200us
    tv.tv_sec = 0;
    fd_set read_fd;
    char recv_buf[512];
    memset(recv_buf, 0 ,sizeof(recv_buf));
 
    int ret = 0;
    FD_ZERO(&read_fd);
    FD_SET(rawsock, &read_fd);
    ret = select(rawsock+1, &read_fd, NULL, NULL, &tv);
    switch(ret)
    {
        case -1:
            fprintf(stderr,"fail to select!\n");
            break;
        case 0:
            break;
        default:
            {
                int size = recv(rawsock, recv_buf, sizeof(recv_buf), 0);
                if(size < 0)
                {
                    fprintf(stderr,"recv data fail!\n");
                }

                ret = icmp_unpack(recv_buf, size, ping_packet); //对接收的包进行解封
                if(ret == -1)  //不是属于自己的icmp包，丢弃不处理
                {
                    break;
                }
            }
            break;
    }

    
    return ret;
}

int net_status_get_delay_ms(char *ip_str, char *if_name)
{
    int rawsock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
    if(rawsock < 0)
    {
        printf("Fail to create socket!\n");
        return -1;
    }

    struct sockaddr_in dest = {0};
    int inaddr = inet_addr(ip_str);
    dest.sin_family = AF_INET;
    memcpy((char*)&dest.sin_addr, &inaddr, sizeof(inaddr));
    struct ifreq ifr;
    memset(&ifr, 0, sizeof(ifr));
    strncpy(ifr.ifr_name, if_name, IFNAMSIZ-1);
    if (setsockopt(rawsock, SOL_SOCKET, SO_BINDTODEVICE, (void*)&ifr, sizeof(ifr)) < 0) {
        printf("setsockopt error\n");
        close(rawsock);
        return -1;
    } 
    ping_packet_status packet_status  = {0};
    ping_send_(rawsock, &packet_status, &dest);
    int delay_ms = ping_recv_(rawsock, &packet_status);
    close(rawsock);
    return delay_ms;
}

int main()
{
    while(1)
    {
      int delay_ms = net_status_get_delay_ms("8.8.8.8", "ens33");
      printf("delay_ms: %d\n", delay_ms);
      sleep(1);
    }
   
    return 0;
}